/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2002-2015, Open Source Geospatial Foundation (OSGeo)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */

package org.geotools.jdbc;

import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.geotools.api.data.DataStore;
import org.geotools.util.factory.GeoTools;

/**
 * Abstract implementation of DataStoreFactory for jdbc datastores which obtain a JNDI connection.
 *
 * <p>Subclasses should not need to override any methods, only just call the parent constructor
 * passing in the non JNDI datastore factory to delegate to.
 *
 * @author Christian Mueller
 */
public abstract class JDBCJNDIDataStoreFactory extends JDBCDataStoreFactory {

    public static final String J2EERootContext = "java:comp/env/";
    /** JNDI data source name */
    public static final Param JNDI_REFNAME =
            new Param(
                    "jndiReferenceName",
                    String.class,
                    "JNDI data source",
                    true,
                    J2EERootContext + "jdbc/mydatabase");

    /** regular datastore factory to delegate to. */
    protected JDBCDataStoreFactory delegate;

    protected JDBCJNDIDataStoreFactory(JDBCDataStoreFactory delegate) {
        this.delegate = delegate;
    }

    /**
     * Override which explicitly returns null because with a JNDI connection the driver is not known
     * ahead of time.
     */
    @Override
    protected String getDriverClassName() {
        return null;
    }

    /**
     * Override which explicitly returns null, validation queries are not supported, my be part of
     * the external data source configuration
     */
    @Override
    protected String getValidationQuery() {
        return null;
    }

    /**
     * Override which explicitly returns null since there is no jdbc url, the connection is
     * identified by the JNDI name.
     */
    @Override
    protected String getJDBCUrl(Map<String, ?> params) throws IOException {
        return null;
    }

    /** Override to create the datasource from the external JNDI conection. */
    @Override
    protected DataSource createDataSource(Map<String, ?> params, SQLDialect dialect)
            throws IOException {
        String jndiName = (String) JNDI_REFNAME.lookUp(params);
        if (jndiName == null) throw new IOException("Missing " + JNDI_REFNAME.description);

        DataSource ds = null;

        try {
            ds = (DataSource) GeoTools.jndiLookup(jndiName);
        } catch (NamingException e1) {
            // check if the user did not specify "java:comp/env"
            // and this code is running in a J2EE environment
            try {
                if (jndiName.startsWith(J2EERootContext) == false) {
                    ds = (DataSource) GeoTools.jndiLookup(J2EERootContext + jndiName);
                    // success --> issue a waring
                    Logger.getLogger(this.getClass().getName())
                            .log(
                                    Level.WARNING,
                                    "Using "
                                            + J2EERootContext
                                            + jndiName
                                            + " instead of "
                                            + jndiName
                                            + " would avoid an unnecessary JNDI lookup");
                }
            } catch (NamingException e2) {
                // do nothing, was only a try
            }
        }

        if (ds == null) throw new IOException("Cannot find JNDI data source: " + jndiName);
        else return ds;
    }

    /**
     * Determines if the datastore is available.
     *
     * <p>Check in an Initial Context is available, that is all what can be done Checking for the
     * right jdbc jars in the classpath is not possible here
     */
    @Override
    public boolean isAvailable() {
        return GeoTools.isJNDIAvailable();
    }

    /** Override to omit all those parameters which define the creation of the connection. */
    @Override
    protected void setupParameters(Map<String, Object> parameters) {
        parameters.put(
                DBTYPE.key,
                new Param(
                        DBTYPE.key,
                        DBTYPE.type,
                        DBTYPE.description,
                        DBTYPE.required,
                        getDatabaseID()));
        parameters.put(JNDI_REFNAME.key, JNDI_REFNAME);
        parameters.put(SCHEMA.key, SCHEMA);
        parameters.put(NAMESPACE.key, NAMESPACE);
        parameters.put(FETCHSIZE.key, FETCHSIZE);
        parameters.put(BATCH_INSERT_SIZE.key, BATCH_INSERT_SIZE);
        parameters.put(EXPOSE_PK.key, EXPOSE_PK);
        parameters.put(PK_METADATA_TABLE.key, PK_METADATA_TABLE);
        parameters.put(SQL_ON_BORROW.key, SQL_ON_BORROW);
        parameters.put(SQL_ON_RELEASE.key, SQL_ON_RELEASE);
        parameters.put(CALLBACK_FACTORY.key, CALLBACK_FACTORY);
    }

    @Override
    protected String getDatabaseID() {
        return delegate.getDatabaseID();
    }

    @Override
    public String getDisplayName() {
        return delegate.getDisplayName() + " (JNDI)";
    }

    @Override
    public String getDescription() {
        return delegate.getDescription() + " (JNDI)";
    }

    @Override
    protected JDBCDataStore createDataStoreInternal(JDBCDataStore dataStore, Map<String, ?> params)
            throws IOException {
        return delegate.createDataStoreInternal(dataStore, params);
    }

    @Override
    public DataStore createNewDataStore(Map<String, ?> params) throws IOException {
        return delegate.createNewDataStore(params);
    }

    @Override
    public Map<java.awt.RenderingHints.Key, ?> getImplementationHints() {
        return delegate.getImplementationHints();
    }

    @Override
    protected SQLDialect createSQLDialect(JDBCDataStore dataStore) {
        return delegate.createSQLDialect(dataStore);
    }

    @Override
    protected boolean checkDBType(Map<String, ?> params) {
        return delegate.checkDBType(params);
    }
}
